#ifndef COLLADA_H_
#define COLLADA_H_

#include <vector>
#include <string>
#ifdef WIN32
#include<hash_map>
#else
#include <ext/hash_map>
#endif
#include <iostream>
#include<expat.h>
#include<stdio.h>
#include<string.h>


#include<boost/shared_ptr.hpp>
#include<boost/unordered_map.hpp>

#define BUFSIZE 4096

#ifdef XML_LARGE_SIZE
#if defined(XML_USE_MSC_EXTENSIONS) && _MSC_VER < 1400
#define XML_FMT_INT_MOD "I64"
#else
#define XML_FMT_INT_MOD "ll"
#endif
#else
#define XML_FMT_INT_MOD "l"
#endif

#include<map>

using namespace std;

namespace Collada
{
struct DictionaryEntry
{
    std::string id;
    std::string type;

    DictionaryEntry(std::string aid, std::string atype)
    {
        id = aid;
        type = atype;
    }
    DictionaryEntry()
    {
        id = "";
        type = "";
    }

    virtual ~DictionaryEntry(){}
};

struct Dictionary
{
    boost::unordered_map<std::string, boost::shared_ptr<DictionaryEntry> > storage;

    Dictionary()
    {

    }
    ~Dictionary()
    {
    }

    void AddEntry(std::string id,boost::shared_ptr<DictionaryEntry>entry)
    {
		storage[id] = boost::shared_ptr<DictionaryEntry>(entry);
    }

    boost::shared_ptr<DictionaryEntry> GetEntry(std::string id)
    {
        if (storage.count(id))
            return storage[id];
        else
        {
            std::string exception = "non existent entry ";
            exception.append(id);

            throw exception.c_str();
        }
    }

    void GetElementsByType(std::string type,std::vector<boost::shared_ptr<DictionaryEntry> >&entries)
    {
        for (boost::unordered_map<std::string, boost::shared_ptr<DictionaryEntry> >::iterator it = storage.begin(); it!=storage.end(); it++)
        {
            if (it->second->type == type)
                entries.push_back(it->second);
        }
    }

};

struct FloatArray:DictionaryEntry
{
    std::vector<float>array;
    FloatArray(std::string aid, std::string atype):
        DictionaryEntry(aid,atype)
    {}
};

struct UIntArray:DictionaryEntry
{
    std::vector<unsigned int>array;
    UIntArray(std::string aid, std::string atype):
        DictionaryEntry(aid,atype)
    {}
};



//GEOMETRY structures
struct Source:DictionaryEntry
{
    boost::shared_ptr<FloatArray> array;
    std::string semantic;
    unsigned int stride;
    Source(std::string aid, std::string atype):
        DictionaryEntry(aid,atype)
    {}
};

struct Triangles:DictionaryEntry
{
    std::vector<int>input_offsets;
    int max_offset;
    std::vector<std::string>inputs;
    boost::shared_ptr<UIntArray>indecies;
    Triangles(std::string aid, std::string atype):
        DictionaryEntry(aid,atype)
    {max_offset = 0;}
};


struct Geometry:DictionaryEntry
{
    std::vector<std::string>triangles;
    boost::unordered_map<std::string,std::vector<std::string> >vertices;
    std::vector<unsigned int> offsets;
    Geometry(std::string aid, std::string atype):
    DictionaryEntry(aid,atype)
    {

    }
    ~Geometry()
    {
    }
};

//END OF Geometry structures
struct Transform
{
    std::string name;
    std::vector<float>values;
};
struct Node:DictionaryEntry
{
    std::string instance_geometry;
    std::vector<Transform>transforms;
    Node(std::string aid, std::string atype):
        DictionaryEntry(aid,atype)
    {}
};

struct Scene:public DictionaryEntry
{
    std::vector<std::string>nodes;
    Scene(std::string aid, std::string atype):
        DictionaryEntry(aid,atype)
    {}
};

struct Param:public DictionaryEntry
{
    boost::unordered_map<std::string,Param>subparams;
    std::string data;
    Param(std::string aid, std::string atype):
        DictionaryEntry(aid,atype)
    {}
};



struct Image
{
    std::string name;
    std::string path;
};

struct Texture
{
    std::string sampler_id;

};

struct Material
{
    std::string effect_glsl;
    std::string effect_common;
};


struct Technique:public DictionaryEntry
{
    boost::unordered_map<std::string,Param>params;
    Technique(std::string aid, std::string atype):
        DictionaryEntry(aid,atype)
    {}
};

struct Profile:public DictionaryEntry
{
    std::vector<Technique>techniques;
    boost::unordered_map<std::string,Param>params;
//    Profile(std::string aid, std::string atype):
//        DictionaryEntry(aid,atype)
//    {}


};

struct Effect:public DictionaryEntry
{
    Profile profile_common;
    Profile profile_glsl;
    //Profile profile_hlsl;
    //Profile profile_cg;


    Effect(std::string aid, std::string atype):
        DictionaryEntry(aid,atype)
    {}
};


//Local stuff;

struct attribute
{
    std::string name;
    std::string value;
    attribute(std::string aname,std::string avalue)
    {
        name = aname;
        value = avalue;
    }
};

struct State
{
    std::vector<boost::shared_ptr<DictionaryEntry> >entries;
    std::vector<std::string>tags;
    std::vector<std::string>stacked_data;
    int depth;
    std::string path()
    {
        std::string ret;
        for( std::vector<std::string>::iterator it = tags.begin(); it != tags.end(); it++)
        {
            ret.append(1,'/');
            if(*it=="Geometry")
                ret+="mesh";
            else
                ret+=(*it);
        }
        return ret;
    }
};

class Parser
{
private:
    State	state;
public:

    Dictionary dict;
    static int string_to_uint_array(std::string s,std::vector<unsigned int>&retvec)
    {
        std::string buffer;
        if (s.size()==0)
            return 1;
        for (std::string::iterator str = s.begin(); str != s.end(); str++)
        {
            if (*str==' '||*str=='\t')
            {
                if (buffer.size()>0)
                {
                    unsigned int i = atoi(buffer.c_str());
                    retvec.push_back(i);
                }
                buffer.clear();
            }
            else
            {
                buffer.append(1,*str);
            }
        }

        if (buffer.size()>0)
        {

            unsigned int i = atoi(buffer.c_str());
            retvec.push_back(i);
        }
        return 0;
    }

    static int string_to_float_array(std::string s,std::vector<float>&retvec)
    {
        std::string buffer;

        if (s.size()<1)
            return 1;

        for (std::string::iterator str = s.begin(); str != s.end(); str++)
        {
            if (*str==' '||*str=='\t')
            {
                if (buffer.size() > 0)
                {
                    float f = (float)atof(buffer.c_str());
                    retvec.push_back(f);
                }
                buffer.clear();
            }
            else
            {
                buffer.append(1,*str);
            }
        }
        if (buffer.size()>0)
        {
            float i = (float)atof(buffer.c_str());
            retvec.push_back(i);
        }

        return 0;
    }

    //In here we accumulate the data inside the tags. It may be separated into several chunks so we can only pars it in endTag.
    static void process_data(void *objRef, const XML_Char *s, int len)
    {

        State *stateRef = &((Parser*)objRef)->state;
        std::string tagpath = ((State*)stateRef)->path();



        if (((State*)stateRef)->stacked_data.size()>0)
        {
            ((State*)stateRef)->stacked_data.rbegin()->append(std::string(s,len));
        }

    }
    ///@TODO: replace shared_ptr casting with boost::compatible one.
    static void start(void *objRef, const char *el, const char **attr)
    {
        static boost::shared_ptr<DictionaryEntry>empty;
        State* stateRef = &((Parser*)objRef)->state;
        int i = 0;
        std::vector<attribute>atts;
        hash_map<std::string, std::string> attributes;
        for (i = 0; attr[i]; i += 2)
        {
            attributes[attr[i]] = attr[i+1];
        }
        ((State*)stateRef)->stacked_data.push_back("");
        ((State*)stateRef)->tags.push_back(el);
        std::string tagpath = ((State*)stateRef)->path();
        //Creating the Float array and putting binding it to the previous node which MUST be source node. Putting it to the stack.

        if (tagpath == "/COLLADA/library_geometries/geometry/mesh/source/float_array")
        {
             boost::shared_ptr<FloatArray>array (new FloatArray(attributes["id"],"float_array"));
            ((Source*)(((State*)stateRef)->entries.rbegin()->get()))->array = array;
            ((Parser*)objRef)->dict.AddEntry(attributes["id"],array);
            ((State*)stateRef)->entries.push_back(array);

        }
        //Creating geometry node. Putting it to the stack.

        if (tagpath == "/COLLADA/library_geometries/geometry")
        {
            boost::shared_ptr<Geometry> geom (new Geometry(attributes["id"],"geometry"));
            ((Parser*)objRef)->dict.AddEntry(attributes["id"],geom);
            ((State*)stateRef)->entries.push_back(geom);

        }
        //There is 2 diffrent source nodes. One related to material. Another one to geometry.
        if (tagpath == "/COLLADA/library_geometries/geometry/mesh/source")
        {
            if (((State*)stateRef)->entries.size() > 0)
            {
                boost::shared_ptr<DictionaryEntry>entry =(*((State*)stateRef)->entries.rbegin());
                boost::shared_ptr<Source>array(new Source(attributes["id"],"source"));
                ((Parser*)objRef)->dict.AddEntry(array->id,array);
                ((State*)stateRef)->entries.push_back(array);
            }
        }
        //Attaching stride to the source-node.
        else if ( tagpath == "/COLLADA/library_geometries/geometry/mesh/source/technique_common/accessor")
        {
            ((Source*)(((State*)stateRef)->entries.rbegin()->get()))->stride = atoi(attributes["stride"].c_str());
        }
        else if (tagpath == "/COLLADA/library_geometries/geometry/mesh/triangles")
        {
            ((Geometry*)(((State*)stateRef)->entries.rbegin()->get()))->triangles.push_back((*((State*)stateRef)->entries.rbegin())->id+"_triangles");
            boost::shared_ptr<Triangles>array(new Triangles((((State*)stateRef)->entries.rbegin()->get())->id+"_triangles","triangles"));
            ((Parser*)objRef)->dict.AddEntry(array->id,array);
//            array->material = attributes["material"];
            ((State*)stateRef)->entries.push_back(array);
        }
        //Creating vertices to define which kind of input we have. Triangles or vertices. It's only used because of ovecomplication of COLLADA.
        else if ( tagpath == "/COLLADA/library_geometries/geometry/mesh/vertices")
        {
            boost::shared_ptr<DictionaryEntry> vt(new DictionaryEntry(attributes["id"],"vertices"));
            ((State*)stateRef)->entries.push_back(vt);
        }
        //We either attach inputs to triangles or create the dictionary to solve the inputs later.

        else if (tagpath == ("/COLLADA/library_geometries/geometry/mesh/triangles/input"))
        {
            ((Triangles*)(((State*)stateRef)->entries.rbegin()->get()))->inputs.push_back(attributes["source"].erase(0,1));
             ((Triangles*)(((State*)stateRef)->entries.rbegin()->get()))->input_offsets.push_back(atoi(attributes["offset"].c_str()));
            ((Triangles*)(((State*)stateRef)->entries.rbegin()->get()))->max_offset =
            MAX(((Triangles*)(((State*)stateRef)->entries.rbegin()->get()))->max_offset,atoi(attributes["offset"].c_str()));

            if(attributes["semantic"].compare("VERTEX") != 0)
                ((Source*)((Parser*)objRef)->dict.GetEntry(attributes["source"]).get())->semantic = attributes["semantic"];
        }
        else if (tagpath == ("/COLLADA/library_geometries/geometry/mesh/vertices/input"))
        {
            boost::shared_ptr<DictionaryEntry>entry2 (* (++ ((State*)stateRef)->entries.rbegin()));
            ((boost::static_pointer_cast<Geometry>(entry2)))->vertices[(*((State*)stateRef)->entries.rbegin())->id].push_back(attributes["source"].erase(0,1));
            ((Source*)((Parser*)objRef)->dict.GetEntry(attributes["source"]).get())->semantic = attributes["semantic"];

        }

        //Parsing the indecies and attaching it to triangle node.
        else if (tagpath == "/COLLADA/library_geometries/geometry/mesh/triangles/p")
        {
            boost::shared_ptr<UIntArray>array(new UIntArray((*((State*)stateRef)->entries.rbegin())->id+"_indecies","indecies"));
			boost::static_pointer_cast<Triangles>(*(((State*)stateRef)->entries.rbegin()))->indecies = array;
			((Parser*)objRef)->dict.AddEntry(array->id,array);
            ((State*)stateRef)->entries.push_back(array);
        }


        if (tagpath == "/COLLADA/library_visual_scenes/visual_scene")
        {

            boost::shared_ptr<Scene>scene(new Scene(attributes["id"],"visual_scene"));
            ((Parser*)objRef)->dict.AddEntry(attributes["id"],scene);
            ((State*)stateRef)->entries.push_back(scene);
        }
        else if (tagpath == "/COLLADA/library_visual_scenes/visual_scene/node")
        {
            ((Scene*)(((State*)stateRef)->entries.rbegin()->get()))->nodes.push_back(attributes["id"]);
             boost::shared_ptr<Node> node(new Node(attributes["id"],"node"));
            ((Parser*)objRef)->dict.AddEntry(attributes["id"],node);
            ((State*)stateRef)->entries.push_back(node);
        }

        else if (tagpath == "/COLLADA/library_visual_scenes/visual_scene/node/instance_geometry")
        {
            ((Node*)(((State*)stateRef)->entries.rbegin()->get()))->instance_geometry = attributes["url"].erase(0,1);
        }

        ///@Part pasing effects library
        else if(tagpath == "/COLLADA/library_effects/effect")
        {
            boost::shared_ptr<Effect>effect (new Effect(attributes["id"],"effect"));
            ((Parser*)objRef)->dict.AddEntry(attributes["effect"],effect);
            ((State*)stateRef)->entries.push_back(effect);
        }
        else if(tagpath == ("/COLLADA/library_effects/effect/profile_COMMON/technique/newparam"))
        {
            //boost::static_pointer_cast<Effect>(*(((State*)stateRef)->entries.rbegin()))->
        }
        else if(tagpath.find("/COLLADA/library_effects/effect/profile_COMMON/technique/phong")==0)
        {
        }
        else if(tagpath == "/COLLADA/library_effects/effect/profile_COMMON/technique/blinn")
        {
        }
        else if(tagpath == "/COLLADA/library_effects/effect/profile_GLSL")
        {
        }
        else if(tagpath == "/COLLADA/library_effects/effect/profile_GLSL/code")
        {
        }
        else if(tagpath == "/COLLADA/library_effects/effect/profile_GLSL/newparam")
        {
        }
        else if(tagpath == "/COLLADA/library_effects/effect/profile_GLSL/technique")
        {
        }
        else if(tagpath == "/COLLADA/library_effects/effect/profile_GLSL/technique/pass")
        {
        }
    }

    static void XMLCALL end(void *objRef, const char *el)
    {
        State* stateRef = &((Parser*)objRef)->state;
        //Parsing the float array and removing it from stack.
        std::string tagpath = ((State*)stateRef)->path();
        if (tagpath == "/COLLADA/library_geometries/geometry/mesh/source/float_array")
        {
            std::vector<float>buffs;
            string_to_float_array(*((State*)stateRef)->stacked_data.rbegin(),buffs);
            boost::shared_ptr<FloatArray> array = boost::static_pointer_cast<FloatArray>( (*((State*)stateRef)->entries.rbegin()) );
            array->array.resize(buffs.size());
            std::copy(buffs.begin(),buffs.end(),array->array.begin());
            ((State*)stateRef)->entries.pop_back();

        }

        //Parsing the indecies array and removing it from stack.
        else if (tagpath == "/COLLADA/library_geometries/geometry/mesh/triangles/p")
        {
            std::vector<unsigned int>buffs;
            string_to_uint_array(*((State*)stateRef)->stacked_data.rbegin(),buffs);
            boost::shared_ptr<UIntArray>array = boost::static_pointer_cast<UIntArray>( (*((State*)stateRef)->entries.rbegin()));
            array->array.resize(buffs.size());
            std::copy(buffs.begin(),buffs.end(),array->array.begin());
            ((State*)stateRef)->entries.pop_back();
        }
        //We only remove anything from stack if it's part of geometry
        if (tagpath == "/COLLADA/library_geometries/geometry/mesh/source")
        {
            if (((State*)stateRef)->entries.size() > 1)
            {
                boost::shared_ptr<DictionaryEntry> entry = *(++((State*)stateRef)->entries.rbegin());
                if (entry->type.compare("geometry")==0)
                    ((State*)stateRef)->entries.pop_back();
            }
        }
        //The other tags doesn't require any processing except removing the entry from stack.
        else if (tagpath == "/COLLADA/library_geometries/geometry/mesh/vertices")
        {
            ((State*)stateRef)->entries.pop_back();
        }
        if (tagpath == "/COLLADA/library_geometries/geometry")
        {
            ((State*)stateRef)->entries.pop_back();
        }
        else if (tagpath == "/COLLADA/library_geometries/geometry/mesh/triangles")
        {
            ((State*)stateRef)->entries.pop_back();
        }

        if (tagpath == "/COLLADA/library_visual_scenes/visual_scene")
        {
            ((State*)stateRef)->entries.pop_back();
        }
        else if (tagpath == "/COLLADA/library_visual_scenes/visual_scene/node")
        {
            ((State*)stateRef)->entries.pop_back();
        }
        else if (tagpath == "/COLLADA/library_visual_scenes/visual_scene/node/instance_geometry")
        {
        }
        //Here we need to parse transforms
        else if (tagpath == "/COLLADA/library_visual_scenes/visual_scene/node/translate" ||
                 tagpath == "/COLLADA/library_visual_scenes/visual_scene/node/scale" ||
                 tagpath.find("/COLLADA/library_visual_scenes/visual_scene/node/rotate") == 0)
        {
            boost::shared_ptr<Node> node = boost::static_pointer_cast<Node>(*((State*)stateRef)->entries.rbegin());
            Transform transform;
            transform.name = el;
            string_to_float_array(*((State*)stateRef)->stacked_data.rbegin(),transform.values);
            node->transforms.push_back(transform);
        }
        ///@Part pasing effects library
        else if(tagpath == "/COLLADA/library_effects/effect")
        {
            ((State*)stateRef)->entries.pop_back();
        }
        else if(tagpath == "/COLLADA/library_effects/effect/profile_COMMON")
        {
        }
        else if(tagpath == "/COLLADA/library_effects/effect/profile_COMMON/technique")
        {
        }
        else if(tagpath.find("/COLLADA/library_effects/effect/profile_COMMON/technique/phong")==0)
        {
        }

        //We don't need data after we leave tag anyway.
        ((State*)stateRef)->stacked_data.pop_back();
        ((State*)stateRef)->tags.pop_back();
    }

    struct tmp_vert
    {
        std::vector<unsigned int> indecies;
        bool operator == (const tmp_vert& dst)
        {
            return  std::equal( indecies.begin(), indecies.end(), dst.indecies.begin() );
        }
    };

    ///@DESC: Opengl doesn't allow multiple indecies for one vertex, so we need to bake everyting to single vertex.
    ///		  it will take more memory but we don't have any choice.
    void bake_geometry_opengl(boost::shared_ptr<Geometry>geom)
    {
        for (std::vector<std::string>::iterator tri_it = geom->triangles.begin(); tri_it != geom->triangles.end(); tri_it++)
        {
            std::vector<tmp_vert>vertices;
            std::vector<unsigned int>indecies;
            std::vector< std::vector< boost::shared_ptr<Source> > >sources;

            boost::shared_ptr<Triangles> trngls = boost::static_pointer_cast<Triangles>(dict.GetEntry(*tri_it));

            if (trngls->indecies->array.size()%trngls->inputs.size()) throw "Incorrect indecies count";
            //Collecting indecies
            for (std::vector<unsigned int>::iterator iit = trngls->indecies->array.begin(); iit != trngls->indecies->array.end();)
            {
                tmp_vert vt;

                for ( int  counter = 0; counter < trngls->max_offset+1; counter++)
                {
                    vt.indecies.push_back(*iit);
                    iit++;
                }

                vertices.push_back(vt);
                indecies.push_back(vertices.size()-1);
            }

            hash_map<std::string,std::vector<float> >source_arrays;
            //Collecting sources
            for (std::vector<std::string>::iterator src_it = trngls->inputs.begin(); src_it != trngls->inputs.end(); src_it++)
            {

                if (geom->vertices.find(*src_it) != geom->vertices.end())
                {
                    std::vector<boost::shared_ptr<Source> > tmp_vect;
                    for (std::vector<std::string>::iterator sname_it = geom->vertices[*src_it].begin(); sname_it !=  geom->vertices[*src_it].end(); sname_it++)
                    {
                        boost::shared_ptr<Source>src = boost::static_pointer_cast<Source>(dict.GetEntry(*sname_it));
                        source_arrays[src->id] = std::vector<float>(vertices.size()*src->stride);
                        tmp_vect.push_back(src);
                    }
                    sources.push_back(tmp_vect);
                }
                else
                {
                    std::vector<boost::shared_ptr<Source> > tmp_vect;
                    boost::shared_ptr<Source>src = boost::static_pointer_cast<Source>(dict.GetEntry(*src_it));
                    source_arrays[src->id] = std::vector<float>(vertices.size()*src->stride);
                    tmp_vect.push_back(src);
                    sources.push_back(tmp_vect);
                }
            }

            //Creating temporary vertice storages
            for (std::vector<std::vector<boost::shared_ptr<Source> > >::iterator s_it = sources.begin(); s_it != sources.end(); s_it++)
            {

            }

            //Copying vertices
            for (std::vector<tmp_vert>::iterator vit = vertices.begin(); vit != vertices.end(); vit++)
            {
                for ( std::vector<std::vector<boost::shared_ptr<Source> > >::iterator sv_it = sources.begin(); sv_it != sources.end(); sv_it++)
                {
                    std::vector<unsigned int>::iterator svit = vit->indecies.begin() + trngls->input_offsets[sv_it - sources.begin()];
                    for (std::vector<boost::shared_ptr<Source> >::iterator s_it = sv_it->begin(); s_it != sv_it->end(); s_it++)
                    {

                        for (unsigned int i = 0; i < (*s_it)->stride; i++)
                        {
                            source_arrays[(*s_it)->id]
                            [(vit - vertices.begin())*(*s_it)->stride+i] =
                                (*s_it)->array->array[*svit * (*s_it)->stride+i];
                        }
                    }
                }
            }

            for ( std::vector<std::vector<boost::shared_ptr<Source> > >::iterator sv_it = sources.begin(); sv_it != sources.end(); sv_it++)
            {
                for (std::vector<boost::shared_ptr<Source> >::iterator s_it = sv_it->begin(); s_it != sv_it->end(); s_it++)
                {
                    (*s_it)->array->array.resize(vertices.size()*(*s_it)->stride);
                    std::copy(source_arrays[(*s_it)->id].begin(),source_arrays[(*s_it)->id].end(),(*s_it)->array->array.begin());
                }
            }
            trngls->indecies->array.resize(indecies.size());
            std::copy(indecies.begin(),indecies.end(),trngls->indecies->array.begin());

        }

    }

    int Parse(const char *name)
    {
        char buffer[2048];
        std::ifstream reader(name,std::ios_base::binary);
        XML_Parser parser = XML_ParserCreate(NULL);
        State state;
        XML_SetUserData(parser, this);
        XML_SetDefaultHandler(parser,process_data);
        XML_SetElementHandler(parser, start, end);

        do
        {
            reader.read(buffer,2048);
            if (XML_Parse(parser, buffer, (int)reader.gcount(), 0) == XML_STATUS_ERROR)
            {
                throw ("Error parsing stuff");
            }
        }
        while (!reader.eof());

        reader.close();
        XML_ParserFree(parser);

        std::vector<boost::shared_ptr<DictionaryEntry> >entries;
        dict.GetElementsByType("geometry",entries);

        for (std::vector<boost::shared_ptr<DictionaryEntry> >::iterator it = entries.begin(); it != entries.end(); it++)
        {
            bake_geometry_opengl( boost::static_pointer_cast<Geometry>(*it));
        }
        return 0;
    }

};

};
#endif
